const fs = require('fs')
const path = require('path')
const babelParser = require('@babel/parser')
const babelTraverse = require('@babel/traverse').default
const babelCore = require('@babel/core')

// 获取单个文件的依赖和编译过后的代码
const bundleFile = (filename) => {
  // 首先拿到入口文件的内容
  const content = fs.readFileSync(filename, 'utf-8')

  // 把入口文件的内容变成一颗语法树
  const ast = babelParser.parse(content, {
    // 由于我们写的是esModule语法，所以要定义类型为module
    sourceType: 'module'
  })

  // 拆分语法树下面每个节点，从而执行不一样的东西
  const dependencies = {}
  babelTraverse(ast, {
    // 如果节点类型为ImportDeclaration时，就是我们写的improt语句
    // 而import的东西，就是该节点需要依赖的东西
    ImportDeclaration ({ node }) {
      // node参数为该节点的语法树
      const dirname = path.dirname(filename)
      // 获取相对路径
      const relativeFileName = node.source.value
      // 获取依赖的绝对路径
      const absoluteFileName = path.join(dirname, relativeFileName)
      dependencies[relativeFileName] = absoluteFileName
    }
  })

  // 然后把语法树的代码编译成浏览器可以读取的代码，就是es6转es5
  const { code } = babelCore.transformFromAst(ast, null, {
    presets: ['@babel/preset-env']
  })

  return {
    filename,
    dependencies,
    code
  }
}

// 遍历文件语法树
const mapBundleFile = (entry) => {
  const bundleModule = bundleFile(entry)
  const bundleArr = [bundleModule]

  for (let i = 0; i < bundleArr.length; i++) {
    const item = bundleArr[i]

    if (item.dependencies) {
      for (let key in item.dependencies) {
        bundleArr.push(
          bundleFile(item.dependencies[key])
        )
      }
    }
  }

  const graph = {}
  bundleArr.forEach((item) => {
    graph[item.filename] = {
      dependencies: item.dependencies,
      code: item.code
    }
  })

  return graph
}

// 生成代码
const generateCode = (entry) => {
  const graph = JSON.stringify(mapBundleFile(entry));

  return `
    (function (graph) {
      function require (entry) {
        function localRequire (relativeFile) {
          return require(graph[entry].dependencies[relativeFile])
        }
        var exports = {};
        (function (require, exports, code) {
          eval(code) 
          // 执行引入模块的代码，但代码会使用到require，而且是相对路径的require
          // 所以需要我们定义新的require，即localRequire，传入去
          // 同时，代码还需要一个exports对象来进行数据的传递
          // 所以，也定义好exports，传入去
        })(localRequire, exports, graph[entry].code)

        // 最后，require函数要导出exprots对象，给引入文件使用
        return exports
      }
      
      require('${entry}') // 首先引入入口模块
    })(${graph})
  `
}

const moduleInfo = generateCode(path.resolve(__dirname, './index.js'))

console.log(moduleInfo)
